﻿#define GESTURE_BERZIER
//#define NOT_USE_MOUSE

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.GamerServices;
using Leap;
using System.IO;
using System.Xml;
using System.Text;
using Accord.Statistics.Models.Markov;
using Accord.Statistics.Models.Markov.Topology;
using Accord.Statistics.Models.Markov.Learning;
using Accord.Statistics.Distributions.Multivariate;
using Accord.Statistics.Distributions.Fitting;
#endregion

namespace GameName2
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Game
    {
        //hmm
        List<Vector2> sequence;
        GestureDB database;
        private const float _delay = 15; // seconds
        private float _remainingDelay = _delay;

        //layout
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D brush;
        Texture2D texIndicator;
        float indicator;
        int scale;
        MouseState current_mouse, previous_mouse;
        float mouseX, mouseY, prevPosX, prevPosY;
        public List<Vector2> coordinates { get; set; }
        Controller leapController;
        SingleListener listener;
        //
        int counterClass=0;

        //hmm
        private HiddenMarkovClassifier<MultivariateNormalDistribution> hmm;
        private ITopology forward;
        public Game1()
            : base()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 200;
            graphics.PreferredBackBufferHeight = 100;
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            //inisialisasi leap motion controller dan listener
            leapController = new Controller();
            listener = new SingleListener();
            leapController.AddListener(listener);
            //inisialisasi sequence yang nantinya akan ambil koordinat gesturenya. terdiri dari x dan y atau vector2
            sequence = new List<Vector2>();
            //inisialisasi database dari gesturenya sebagai tempat untuk menyimpan datanya.
            database = new GestureDB();
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            // load dot indikator dengan gambar titik berwarna merah dan indikator lain bewarna biru
            brush = Content.Load<Texture2D>("reddot");
            texIndicator = Content.Load<Texture2D>("bluedot");
            // buat list dari koordinat untuk visualisasi apa yang akan di gambar
            coordinates = new List<Vector2>();
            
            
            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here
#if NOT_USE_MOUSE
           UpdateMouse();
#endif
            if (counterClass < 1)
            {
                //gunakan timer pada saat pergantian dalam meng-capture gesture dengan leap motion
                var timer = (float)gameTime.ElapsedGameTime.TotalSeconds;
                _remainingDelay -= timer;
            }
            else
            { 
                List<string> classesFromFile = database.ReadFromFilesLog((int)GestureDB.TYPEFILEREADER.TypeClasses);
                double[][][] inputs = database.ReadFromSourcePathLog();
                int[] outputIntLabel = database.ReadFromOutputLog(database.NumberofIO);
                List<string> h3hi = database.ReadFromFilesLog((int)GestureDB.TYPEFILEREADER.TypeRecognizedAs);

                int[] outputlabel = outputIntLabel;

                forward = new Forward(states:5);
                hmm = new HiddenMarkovClassifier<MultivariateNormalDistribution>((int)Convert.ToInt32(classesFromFile.Count),
                    forward, new MultivariateNormalDistribution(2), classesFromFile.ToArray());

                var teacher = new HiddenMarkovClassifierLearning<MultivariateNormalDistribution>(hmm,
                    i => new BaumWelchLearning<MultivariateNormalDistribution>(hmm.Models[i]) 
                    { 
                        Tolerance = 0.01,
                        Iterations = 0,
                        FittingOptions = new NormalOptions()
                        {
                            Regularization = 1e-5
                        }

                    });

                // Run the learning algorithm.. yeeey.. iso berroh
                double error = teacher.Run(inputs, outputlabel);


                // Classify all training instances
                //foreach (var sample in inputs)
                //{
                //    var asas = hmm.Compute(inputs);
                //}
                //apply learning
            }
            //
            if (_remainingDelay <= 0)
            {
                //MyMethod();
                Console.WriteLine(_remainingDelay);
                double[][] result = new double[sequence.Count][];
                //Console.WriteLine("sequence x: {0} y: {1}",sequence.);
                
                //untuk debug...
#if DEBUG_MODE
                for (int i = 0; i < sequence.Count; i++)
                    Console.WriteLine("sequence x: {0} y: {1}", sequence[i].X, sequence[i].Y);
#endif

                // mulai proses pre-prosessing masukkan input sequence dari gesture kedalam fungsi preprosesing
                double[][] input = GestureSequence.Preprocessing(sequence);

               // hmm.Compute(input);

                //string label = database.Classes[index];
                

                //perhitungan waktu mundur dengan mengisi waktu yang tersisa
                _remainingDelay = _delay;
                leapController.RemoveListener(listener);
                listener.Dispose();
                leapController.Dispose();
               
                // masukkan database sequence
                switch(counterClass)
                {
                    case 0:
                        database.Add(sequence, "Kotak",0);
                        break;
                    case 1:
                        database.Add(sequence, "Lingkaran",1);
                        break;
                    case 2:
                        database.Add(sequence, "Segitiga",2);
                        break;
                }

                counterClass++;
                this.Initialize();
            }
            else
            {
                //proses gesture
                if (listener != null)
                {
                    foreach (FingerPointStorage f in listener.fingerPoint)
                    {
                        if (f.isActive)
                        {
                            float xObj = f.g_X*10;
                            float yObj = f.g_Y*-10;
                            //mengambil posisi z sebagai deteksi kedalaman
                            if (f.g_Z > 0)
                            {
                                indicator = 150 - f.g_Z;
                                Console.WriteLine("Leap indicator: {0}", indicator);
                                //jika pada kedalaman tertentu maka akan dimulai menggambar ditandai dengan skala pada dot 
                                //indicator dengan ukuran 20 pixel
                                if (indicator > 50)
                                {
                                    //gambar pada screen, gesture yang diambil dengan leap motion controller
                                    //terdapat 2 cara, dengan dot per dot berzier atau hanya sequence dari dot per dot
                                    coordinates.Add(new Vector2(f.g_X, f.g_Y));                             
                                    //
                                    //sequence.Add(new Vector2(f.g_X, f.g_Y));
                                    scale = 20;
                                }
                                //kondisi jika kedalaman tangan belum sesuai maka dot indikator diberi skala lebih besar
                                else if (indicator > 20 && indicator <= 50)
                                {
                                    scale = 30;
                                }
                                else
                                {
                                    scale = 45;
                                }
                            }
                        }
                    }
                }
            }

            // TODO: Add your update logic here        
            base.Update(gameTime);
        }

        //save adding gesture
        private void addGesture()
        { 
        
        }
#if NOT_USE_MOUSE
        // Update Mouse Method
        protected void UpdateMouse()
        {
            // Set the previous Mouse postion to what the current mouse is
            previous_mouse = current_mouse;
            // Set the current Mouse position to... well the current mouse position 
            current_mouse = Mouse.GetState();

            // set the mouseX and mouseY values to that of the current mouse states X and Y values
            // This is used to update the position of the brush itself
            mouseX = current_mouse.X;
            mouseY = current_mouse.Y;

            // set the prevPosX and prevPosY values to that of the previous Mouse X and Y positions
            // This is then added to the list and then used as the position for painting the stroke
            prevPosX = previous_mouse.X;
            prevPosY = previous_mouse.Y;
        }
#endif
#if GESTURE_BERZIER
        void DrawLine(SpriteBatch sb,Texture2D t, Vector2 start, Vector2 end)
        {
            //hitung jarak antara titik akhir ke titik awal
            Vector2 edge = end - start;

            // hitung sudut untuk membuat garis yang bisa melingkar
            float angle =
                (float)Math.Atan2(edge.Y, edge.X);

            if (start != end)
            {
                sb.Draw(t,
                    new Rectangle(// gambar kotak untuk mendefinisikan gambaran garis dan posisi dari garis awal
                        (int)start.X,
                        (int)start.Y,
                        (int)edge.Length(), //spritebach akan menarik teksture untuk mengisi kotak ini dengan panjang dari edge
                        5), //ukuran dari garis tersebut, bisa lebih tebal atau tipis jika ini diganti
                    null,
                    Color.AliceBlue, //warna garis
                    angle,     //sudut dari garis
                    new Vector2(0, 0), // titik pada garis untuk rotasi
                    SpriteEffects.None,
                    0);
                sequence.Add(end);
                                    
            }
        }
#endif
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
            // memperlihatkan dot indikator pada saat tangan di gerakkan dengan LMC
            foreach(FingerPointStorage f in listener.fingerPoint) if(f.isActive)
                    spriteBatch.Draw(texIndicator, new Rectangle((int)f.g_X, (int)f.g_Y, scale, scale), Color.White);
#if GESTURE_BERZIER
            /*
             * dapatkan semua posisi dot indikator dan memanggil fungsi drawline untuk membuat garis lurus atau melengkung
             * dengan kurva berzier dari posisi awal ke posisi akhir kemudian di gambar
             */
            for (int i = 1; i < coordinates.Count; i++)
            {
                 DrawLine(spriteBatch, //draw line
                   brush,
                   new Vector2(coordinates[i].X, coordinates[i].Y), //start of line
                   new Vector2(coordinates[i - 1].X, coordinates[i - 1].Y) //end of line
               );
            }
#else
             /*
             * dapatkan semua posisi dot indikator dan langsung digambar
             */
            foreach (Vector2 vect in coordinates)
            {
                spriteBatch.Draw(brush, new Rectangle((int)vect.X, (int)vect.Y, 30, 30), Color.White);
            }
#endif
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}
